/**
 * Copyright 2020 Huawei Technologies Co., Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "aicpu_graph_optimizer.h"

#include "common/util/platform_info.h"
#include "config/config_file.h"
#include "engine/base_engine.h"
#include "error_code/error_code.h"
#include "ge/ge_api_types.h"
#include "graph_optimizer_utils.h"
#include "optimizer.h"
#include "util/log.h"
#include "util/util.h"
#include "util/constant.h"

using namespace ge;
using namespace std;

namespace {
constexpr uint64_t kAutoCastModeOff = 0;
constexpr uint64_t kAutoCastModeOn = 1;
}

namespace aicpu {
ge::Status AicpuGraphOptimizer::Initialize(const map<string, string> &options) {
  // initial optimizers
  auto iter = options.find(ge::SOC_VERSION);
  AICPU_IF_BOOL_EXEC(iter == options.end(),
      AICPU_REPORT_INNER_ERROR(
          "cannot find [%s] in param of optimizer initialize function.",
          ge::SOC_VERSION.c_str());
      return INPUT_PARAM_VALID)
  soc_version_ = iter->second;
  std::string auto_cast_mode;
  if (ConfigFile::GetInstance().GetValue(kAutoCastMode, auto_cast_mode)) {
    auto_cast_mode_ = kAutoCastModeOff;
    if (StringToNum(auto_cast_mode, auto_cast_mode_).state != ge::SUCCESS) {
      AICPUE_LOGW("Tran auto_cast_mode[%s] to integer failed. default value is [%d].",
                  auto_cast_mode.c_str(), auto_cast_mode_);
    }
  } else {
    AICPUE_LOGW("Get Value[AutoCastMode] failed");
  }
  AICPU_CHECK_RES(Finalize())
  string optimizers_str;
  string optimizers_config = Stringcat(engine_name_, "GraphOptimizer");
  AICPU_IF_BOOL_EXEC(
      !ConfigFile::GetInstance().GetValue(optimizers_config, optimizers_str),
      AICPU_REPORT_INNER_ERROR("[%s] not exist.", optimizers_config.c_str());
      return LOAD_OPTIMIZER_CONFIG_FAILED)
  vector<string> optimizers;
  ConfigFile::GetInstance().SplitValue(optimizers_str, optimizers);
  for (auto optimizer : optimizers) {
    FACTORY_GRAPH_OPTIMIZER::FactoryType optimizers_ptr =
        FACTORY_GRAPH_OPTIMIZER::Produce(optimizer);
    AICPU_IF_BOOL_EXEC(optimizers_ptr == nullptr,
        AICPU_REPORT_INNER_ERROR("[%s] instantiate failed", optimizer.c_str());
        return GRAPH_OPTIMIZER_INSTANCE_FAILED)
    optimizer_map_[optimizer] = optimizers_ptr;
    AICPU_CHECK_RES(optimizers_ptr->Initialize());
  }
  return SUCCESS;
}

ge::Status AicpuGraphOptimizer::Finalize() {
  optimizer_map_.clear();
  return SUCCESS;
}

ge::Status AicpuGraphOptimizer::GetAttributes(
    GraphOptimizerAttribute &attrs) const {
  attrs.engineName = engine_name_;
  return SUCCESS;
}

ge::Status AicpuGraphOptimizer::OptimizeOriginalGraphJudgeInsert(
    ComputeGraph &graph) {
  if (IsEmptyGraph(graph)) {
    return SUCCESS;
  }
  map<string, OpFullInfo> all_op_info;
  AICPU_CHECK_RES(GetOpsInfo(all_op_info));
  for (auto optimizer : optimizer_map_) {
    AICPU_CHECK_RES(
        optimizer.second->OptimizeOriginalGraphJudgeInsert(graph, all_op_info))
  }
  return SUCCESS;
}

ge::Status AicpuGraphOptimizer::OptimizeFusedGraph(ComputeGraph &graph) {
  string graph_name = graph.GetName();
  AICPUE_LOGI("begin to optimizer graph[%s]", graph_name.c_str());
  if (IsEmptyGraph(graph)) {
    return SUCCESS;
  }
  // vertify placehold and end node make sure format equals client format
  AICPU_CHECK_RES(GraphOptimizerUtils::VerifyPldAndEndNode(graph))

  string suffix = "Before_Aicpu_Optimized";
  GraphOptimizerUtils::DumpGraph(graph, suffix);

  fe::PlatFormInfos platform_infos;
  fe::OptionalInfos opt_infos;
  AICPU_IF_BOOL_EXEC(
      fe::PlatformInfoManager::Instance().GetPlatformInfos(soc_version_,
          platform_infos, opt_infos) != ge::GRAPH_SUCCESS,
      AICPU_REPORT_CALL_ERROR("Call fe::PlatformInfoManager::GetPlatformInfos "
          "function failed. soc version[%s]", soc_version_.c_str());
      return ge::FAILED)
  // cache coherence need to be guaranteed
  string aicpu_cache_enable;
  AICPU_IF_BOOL_EXEC(
      !platform_infos.GetPlatformRes("CPUCache", "AICPUSyncBySW", aicpu_cache_enable),
      AICPU_REPORT_CALL_ERROR("Call fe::PlatFormInfos::GetPlatformRes failed to"
          " get aicpu cache synchronous status");
      return ge::FAILED)
  AICPUE_LOGI("aicpu cache enable flag[%s]", aicpu_cache_enable.c_str());
  if (aicpu_cache_enable.find("1") != string::npos) {
    AICPU_CHECK_RES_WITH_LOG(CacheGraph::GenerateNoCacheGraph(graph),
        "Call GenerateCacheGraph function failed, graph[%s].",
        graph_name.c_str())
    suffix = "After_Insert_Cache_op";
    GraphOptimizerUtils::DumpGraph(graph, suffix);
  }
  map<string, OpFullInfo> all_op_info;
  AICPU_CHECK_RES(GetOpsInfo(all_op_info));

  if (auto_cast_mode_ == kAutoCastModeOn) {
    AICPU_CHECK_RES_WITH_LOG(AutoCastGraph::GenerateAutoCastGraph(graph, all_op_info), "AutoCast failed");
    suffix = "After_Auto_Cast";
    GraphOptimizerUtils::DumpGraph(graph, suffix);
  }
  for (auto optimizer : optimizer_map_) {
    AICPU_CHECK_RES(optimizer.second->OptimizeFusedGraph(graph, all_op_info))
  }
  suffix = "After_Aicpu_Optimized";
  GraphOptimizerUtils::DumpGraph(graph, suffix);
  AICPUE_LOGI("success to optimizer graph[%s]", graph_name.c_str());
  return ge::SUCCESS;
}

bool AicpuGraphOptimizer::IsEmptyGraph(const ComputeGraph &graph) const {
  string graph_name = graph.GetName();
  if (graph.GetDirectNodesSize() == 0) {
    AICPUE_LOGW("No ge node exists in graph[%s].", graph_name.c_str());
    return true;
  }
  return false;
}

ge::Status AicpuGraphOptimizer::GetOpsInfo(
    map<string, OpFullInfo> &all_op_info) const {
  FACTORY_ENGINE::FactoryType engine_ptr = FACTORY_ENGINE::Produce(engine_name_);
  AICPU_CHECK_NOTNULL(engine_ptr)
  AicpuOpsKernelInfoStorePtr aicpu_ops_kernel_info_store_ptr =
      engine_ptr->GetAicpuOpsKernelInfoStore();
  AICPU_CHECK_NOTNULL(aicpu_ops_kernel_info_store_ptr)
  aicpu_ops_kernel_info_store_ptr->GetAllOpsFullKernelInfo(all_op_info);
  return ge::SUCCESS;
}
}  // namespace aicpu
